<!--
 * @name: HForm 水平表单组件
 * @Description: 对el-form组件进行封装，实现水平布局的表单，继承el-form的所有属性和方法
 * @Author: 梁青春
 * @Date: 2025-10-14 11:34:52
 * @LastEditTime: 2025-11-11 16:37:07
 * @LastEditors: 梁青春
 * @Features:
 *   - 支持自定义列数布局（默认2列）
 *   - 自动计算每列的栅格宽度
 *   - 完全继承 el-form 的所有属性和事件
 *   - 支持响应式布局和动态列数调整
 *   - 自动包装表单项到栅格系统中
 * @example:
 * <h-form :column="2" :model="formData" :rules="rules" @submit="handleSubmit">
 *   <el-form-item label="姓名" prop="name">
 *     <el-input v-model="form.name" />
 *   </el-form-item>
 *   <el-form-item label="性别" prop="sex">
 *     <el-radio-group v-model="form.sex">
 *       <el-radio label="1">男</el-radio>
 *       <el-radio label="2">女</el-radio>
 *     </el-radio-group>
 *   </el-form-item>
 * </h-form>
-->
<template>
  <el-form ref="formRef" v-bind="$attrs" v-on="formListeners" class="h-form" :readonly="readonly">
    <div class="h-form-row">
      <template v-for="(item, index) in formItems" :key="index">
        <div class="h-form-col">
          <RenderVNode :vnode="item" />
        </div>
      </template>
    </div>
  </el-form>
</template>

<script setup name="HForm">
import { computed, useSlots, ref, getCurrentInstance, Comment, defineComponent } from 'vue'

// 渲染插槽返回的 VNode，确保插槽子节点正确显示
const RenderVNode = defineComponent({
  name: 'RenderVNode',
  props: {
    vnode: { type: Object, required: true }
  },
  setup(props) {
    return () => props.vnode
  }
})

// ==================== 组件属性定义 ====================
/**
 * 组件属性配置
 * @param {Number} column - 表单列数，默认为2列
 * @param {Number} gutter - 列间距，默认为16px
 */
const props = defineProps({
  // 表单列数：控制一行显示多少个表单项
  column: {
    type: Number,
    default: 2,
    validator: value => value > 0 && value <= 6 // 限制列数在合理范围内
  },
  // 列间距：控制表单项之间的间距
  gutter: {
    type: Number,
    default: 16
  },
  readonly: { type: Boolean, default: false }
})

// ==================== 响应式数据和计算属性 ====================
/**
 * 表单引用：用于暴露 el-form 的方法给父组件
 */
const formRef = ref(null)

/**
 * 获取插槽内容：用于动态渲染表单项
 */
const slots = useSlots()

/**
 * 计算每列的栅格宽度
 * Element Plus 栅格系统总宽度为24，根据列数平均分配
 * @returns {Number} 每列占用的栅格数
 */
const span = computed(() => {
  // 确保计算结果为整数，避免布局问题
  return Math.floor(24 / props.column)
})

/**
 * 计算每个表单项的实际 span 值
 * 如果是当前行的最后一个元素，则占满剩余列宽
 * @param {Number} index - 当前表单项的索引
 * @param {Number} totalItems - 表单项总数
 * @returns {Number} 当前表单项应该占用的栅格数
 */
const getItemSpan = (index, totalItems) => {
  const baseSpan = Math.floor(24 / props.column)
  const currentRow = Math.floor(index / props.column) // 当前行号（从0开始）
  const positionInRow = index % props.column // 在当前行中的位置（从0开始）
  const isLastRow = currentRow === Math.floor((totalItems - 1) / props.column) // 是否为最后一行
  const isLastInRow = positionInRow === props.column - 1 // 是否为行中最后一个
  const isLastItem = index === totalItems - 1 // 是否为最后一个元素

  // 如果是当前行的最后一个元素，或者是整个表单的最后一个元素且不满一行
  if (isLastInRow || (isLastRow && isLastItem)) {
    // 计算剩余的栅格宽度
    const usedSpan = positionInRow * baseSpan
    const remainingSpan = 24 - usedSpan
    return remainingSpan
  }

  return baseSpan
}

/**
 * 响应式栅格配置
 * 在不同屏幕尺寸下自动调整列数，提供更好的移动端体验
 * @returns {Object} 包含不同屏幕尺寸下的栅格配置
 */
const responsiveSpan = computed(() => ({
  xs: 24, // 超小屏幕（<768px）：1列布局
  sm: 12, // 小屏幕（≥768px）：2列布局
  md: span.value // 中等屏幕（≥992px）：使用设定的列数
}))

/**
 * 处理表单项插槽内容
 * 获取默认插槽中的所有表单项，并进行预处理
 * 支持 el-form-item 和 readonly-form-item 组件
 * @returns {Array} 处理后的表单项数组
 */
const formItems = computed(() => {
  // 获取默认插槽内容
  const defaultSlot = slots.default?.() || []

  // 过滤和处理插槽内容，只保留有效的表单项
  return defaultSlot.filter(item => {
    // 过滤掉注释节点和空文本节点
    if (item.type === Comment || (typeof item.type === 'symbol' && item.children?.trim?.() === '')) {
      return false
    }

    // 检查是否为有效的表单项组件
    const componentName = item.type?.name || item.type?.__name || ''
    const tagName = item.type || ''

    // 支持的表单项组件类型：
    // 1. el-form-item (Element Plus 原生表单项)
    // 2. readonly-form-item (只读表单项组件)
    // 3. 其他自定义表单项组件
    const isFormItem =
      tagName === 'el-form-item' ||
      componentName === 'ElFormItem' ||
      tagName === 'readonly-form-item' ||
      componentName === 'ReadonlyFormItem' ||
      componentName === 'readonly-form-item' ||
      // 兼容其他可能的命名方式
      /form-item/i.test(componentName) ||
      /form-item/i.test(tagName)

    return isFormItem || item.type !== 'string' // 保留组件类型的节点
  })
})

// ==================== 事件处理 ====================
/**
 * 获取当前组件实例，用于事件处理
 */
const instance = getCurrentInstance()

/**
 * 表单事件监听器
 * 将父组件传入的所有事件监听器透传给 el-form
 * 这样父组件就可以直接监听 el-form 的所有事件
 * @returns {Object} 事件监听器对象
 */
const formListeners = computed(() => {
  const listeners = {}

  // 遍历父组件传入的所有事件监听器
  if (instance?.vnode?.props) {
    Object.keys(instance.vnode.props).forEach(key => {
      // 识别事件监听器（以 'on' 开头的属性）
      if (key.startsWith('on') && typeof instance.vnode.props[key] === 'function') {
        // 转换事件名称：onSubmit -> submit
        const eventName = key.slice(2).toLowerCase()
        listeners[eventName] = instance.vnode.props[key]
      }
    })
  }

  return listeners
})

// ==================== 暴露给父组件的方法 ====================
/**
 * 暴露 el-form 的所有方法给父组件
 * 这样父组件可以直接调用表单的验证、重置等方法
 */
defineExpose({
  /**
   * 表单验证方法
   * @param {Function} callback - 验证完成后的回调函数
   * @returns {Promise} 验证结果的 Promise
   */
  validate: callback => formRef.value?.validate(callback),

  /**
   * 验证指定字段
   * @param {String|Array} props - 要验证的字段名
   * @param {Function} callback - 验证完成后的回调函数
   * @returns {Promise} 验证结果的 Promise
   */
  validateField: (props, callback) => formRef.value?.validateField(props, callback),

  /**
   * 重置表单
   * 清空所有字段的值和验证状态
   */
  resetFields: () => formRef.value?.resetFields(),

  /**
   * 清空验证信息
   * @param {String|Array} props - 要清空验证信息的字段名，不传则清空所有
   */
  clearValidate: props => formRef.value?.clearValidate(props),

  /**
   * 滚动到指定字段
   * @param {String} prop - 字段名
   */
  scrollToField: prop => formRef.value?.scrollToField(prop)
})
</script>

<style scoped lang="scss">
@use '@/assets/styles/variables.module.scss' as *;

.h-form {
  width: 100%;
}
.h-form-row {
  display: flex;
  grid-template-columns: repeat(3, 1fr);
  flex-wrap: wrap;
  border: solid $border-color;
  border-width: 0 1px 1px 0;
}
.h-form-col {
  flex: 1;
  display: flex;
  align-items: flex-start;
  padding: 0 !important;
  border: solid $border-color;
  border-width: 1px 0 0 1px;
  :deep(.el-form-item__label) {
    width: 120px;
    height: 100%;
    border-right: 1px solid $border-color;
  }
  :deep(.el-form-item__content) {
    min-width: 186px;
    padding: 0 $padding-sm;
    width: 100%;
    height: 100%;
  }
  :deep(.el-date-editor) {
    width: 100%;
  }
  .el-form-item {
    width: 100%;
    margin-bottom: 0 !important;
  }
  :deep(.el-cascader) {
    width: 100%;
  }
  :deep(.el-input) {
    width: 100%;
  }
  :deep(.el-input__wrapper),
  :deep(.el-select__wrapper),
  :deep(.el-textarea__inner) {
    box-shadow: none;
    width: 100%;
    border-radius: 0;
  }
  :deep(.el-input__wrapper:hover),
  :deep(.el-select__wrapper:hover),
  :deep(.el-textarea__inner:hover) {
    box-shadow: none;
  }
}
</style>
